package com.spukmk3me.scenepack.pack0;

import com.spukmk3me.scene.Animator;
import com.spukmk3me.scene.Node;
import com.spukmk3me.util.DoublyLinkedList;
import com.spukmk3me.math.Matrix4f;
import com.spukmk3me.math.Rectangle;

/* $if SPUKMK3ME_DEBUG$ */
import com.spukmk3me.debug.Logger;
/* $endif$ */

public class DefaultNode extends Node
{
	/**
	 *  Create a fake node for private uses
	 */
	DefaultNode()
	{
		m_name = null;
		initDefaultValues();
	}
	
    DefaultNode( DefaultScene owner, String name, Matrix4f matrix )
    {
        m_name      	= name;
        m_clipping  	= new Rectangle();
        m_accAttribute	= new AccumulatedAttribute( owner.getVideoAPI().createMatrix() );
        m_needUpdate	= true;
        initDefaultValues();
    }
    
    private void initDefaultValues()
    {
    	setFlags( VISIBLE | ENABLE | ANIMATE | USEBLENDING );
        m_planeColor = 0xFFFFFFFF;
        m_sx = m_sy = 1.0f;
    }

    @Override
    public String getName()
    {
        return m_name;
    }
    
    @Override
    public Node next()
    {
        if ( c_next == c_parent.c_children )
            return c_parent.c_children.c_next;
        
        return c_next;
    }
    
    @Override
    public Node prev()
    {
        if ( c_prev == c_parent.c_children )
            return c_parent.c_children.c_prev;
        
        return c_prev;
    }
    
    @Override
    public Node parent()
    {
        return c_parent;
    }
    
    @Override
    public Node child()
    {
        if ( c_children == null )
            return null;
        
        return c_children.c_next;
    }
    
    @Override
    public boolean isFirstSibling()
    {
        if ( c_parent == null )
            return true;
        
        return c_prev == c_parent.c_children;
    }
    
    @Override
    public boolean isLastSibling()
    {
        if ( c_parent == null )
            return true;
        
        return c_next == c_parent.c_children;
    }
    
    @Override
    public boolean hasChildren()
    {
        return c_children != null;
    }
    
    @Override
    public boolean hasParent()
    {
        return c_parent != null;
    }
    
    @Override
    public void setFlags( int flags )
    {
        m_flags = flags;
    }
    
    @Override
    public int getFlags()
    {
        return m_flags;
    }
    
    @Override
    public void setVisible( boolean visible )
    {
        if ( visible )
            m_flags |= VISIBLE;
        else
            m_flags &= ~VISIBLE;
    }
    
    @Override
    public boolean getVisible()
    {
        return (m_flags & VISIBLE) != 0;
    }
    
    @Override
    public void setEnable( boolean enable )
    {
        if ( enable )
            m_flags |= ENABLE;
        else
            m_flags &= ~ENABLE;
    }
    
    @Override
    public boolean getEnable()
    {
        return (m_flags & ENABLE) != 0;
    }
    
    @Override
    public void setAnimating( boolean enable )
    {
        if ( enable )
            m_flags |= ANIMATE;
        else
            m_flags &= ~ANIMATE;
    }
    
    @Override
    public boolean getAnimating()
    {
        return (m_flags & ANIMATE) != 0;
    }
    
    @Override
    public void setBlendingUsage( boolean useBlending )
    {
        if ( useBlending )
            m_flags |= USEBLENDING;
        else
            m_flags &= ~USEBLENDING;
    }
    
    @Override
    public boolean getBlendingUsage()
    {
        return (m_flags & USEBLENDING) != 0;
    }
    
    @Override
    public void setClippingUsage( boolean useClip )
    {
        if ( useClip )
            m_flags |= USECLIPPING;
        else
            m_flags &= ~USECLIPPING;
    }
    
    @Override
    public boolean getClippingUsage()
    {
        return (m_flags & USECLIPPING) != 0;
    }
    
    @Override
    public void getClip( Rectangle clipData )
    {
        clipData.copy( m_clipping );
    }
    
    @Override
    public void setClip( Rectangle clipData )
    {
        m_clipping.copy( clipData );
    }

    @Override
    public void setClip( int x, int y, int w, int h )
    {
        m_clipping.set( x, y, w, h ); 
    }
    
    @Override
    public void setPlaneColor( int color )
    {
        m_planeColor = color;
    }
    
    @Override
    public int getPlaneColor()
    {
        return m_planeColor; 
    }
    
    @Override
    public void setAdditionalColor( int color )
    {
        m_additionalColor = color;
    }

    @Override
    public int getAdditionalColor()
    {
        return m_additionalColor;
    }
    
    @Override
    public void setPivot( float x, float y )
    {
    	if ( (x != m_px) || (y != m_py) )
    	{
    		m_px = x;
    		m_py = y;
    		markNeedUpdate( true );
    	}
    }
    
    @Override
    public void setTrans( float x, float y )
    {
    	if ( (x != m_tx) || (y != m_ty) )
    	{
    		m_tx = x;
    		m_ty = y;
    		markNeedUpdate( true );
    	}
    }
    
    @Override
    public void setRot( float angle )
    {
    	if ( angle != m_rotAngle )
    	{
    		m_rotAngle = angle;
    		markNeedUpdate( false );
    	}
    }
    
    @Override
    public void setScale( float x, float y )
    {
    	if ( (x != m_sx) || (y != m_sy) )
    	{
    		m_sx = x;
    		m_sy = y;
    		markNeedUpdate( true );
    	}
    }
    
    @Override
    public void reset()
    {
        m_tx = m_ty = m_px = m_py = m_rotAngle = 0.0f;
        m_sx = m_sy = 1.0f;
        markNeedUpdate( true );
    }
    
    @Override
    public void addAnimator( Animator animator )
    {
        if ( animator == null )
        {
            /* $if SPUKMK3ME_DEBUG$ */
            Logger.trace( "Warning: attempt to pass null as argument.\n" );
            /* $endif$ */
            return;
        }
        
        if ( m_animators == null )
            m_animators = new DoublyLinkedList< Animator >();
        
        if ( m_animators.getIndex( animator ) == -1 )
        {
            if ( animator.getNode() != null )
                animator.getNode().removeAnimator( animator );
            
            m_animators.push_back( animator );
            _attachAnimator( animator );
        }
    }
    
    @Override
    public final void removeAnimator( Animator animator )
    {
        if ( animator == null )
        {
            /* $if SPUKMK3ME_DEBUG$ */
            Logger.trace( "Warning: attempt to pass null as argument.\n" );
            /* $endif$ */
            return;
        }
        
        if ( m_animators != null )
        {
            if ( m_animators.remove( animator ) )
                _detachAnimator( animator );
            /* $if SPUKMK3ME_DEBUG$ */
            else
                Logger.trace( "Warning: attempt to remove an animator which this node doesn't have.\n" );
            /* $endif$ */
            
            if ( m_animators.length() == 0 )
                m_animators = null;
        }
    }
    
    @Override
    public final void removeAllAnimators()
    {
        m_animators = null;
    }
    
    @Override
	public void setRenderOrder( int order )
    {
    	m_rOrder = order;
	}

	@Override
	public int getRenderOrder()
	{
		return m_rOrder;
	}
    
    /**
     *  Utility function for calling animators.
     *  @param deltaTime
     */
	final void runAnimators( int deltaTime )
    {
        if ( m_animators != null )
        {
            DoublyLinkedList< Animator >.Iterator i = m_animators.first();
            DoublyLinkedList< Animator >.Iterator e = m_animators.end();
            Animator animator;
            boolean remove;
            
            for ( ; !i.equals( e ); )
            {
                animator = (Animator)i.data();
                animator.animate( deltaTime );
                remove = false;
                
                if ( (animator.getFlags() & Animator.FLAG_AUTODROP) != 0 )
                {
                    if ( !animator.isAnimating() )
                        remove = true;
                }
                
                i.next();
                
                if ( remove )
                    m_animators.remove( animator );
            }
        }
    }
    
    void addChild( DefaultNode node )
    {
        /* $if SPUKMK3ME_DEBUG$ */
        if ( (node.c_next != null) || (node.c_prev != null) ||
            (node.c_parent != null) )
            Logger.trace( "Adding potentially defected node." );
        /* $endif$ */

        DefaultNode bindingNode;

        if ( c_children == null )
        {
            c_children              = bindingNode = new DefaultNode();
            bindingNode.c_parent    = this;
            bindingNode.c_prev      = bindingNode.c_next = bindingNode;
        }
        else
            bindingNode = c_children;

        node.c_prev = bindingNode.c_prev;
        node.c_next = bindingNode;
        bindingNode.c_prev.c_next   = node;
        bindingNode.c_prev          = node;
        node.c_parent               = this;
    }

    /**
     *  Insert a node after another.
     *  @param node The node that is going to be added.
     *  @param predecessor The node that is supposed to lie right before the
     * added node.
     */
    void insertAfter( DefaultNode predecessor )
    {
        detach();
        
        c_prev = predecessor;
        c_next = predecessor.c_next;
        predecessor.c_next.c_prev   = this;
        predecessor.c_next          = this;
        c_parent = predecessor.c_parent;
    }
    
    /**
     *  Remove this node from its direct father and sibling list.
     *  \details All of its children are retained.
     */
    void detach()
    {
        // This node doesn't have a parent
        if ( (c_prev == null) || (c_next == null) || (c_parent == null) )
            return;
        
        c_prev.c_next = c_next;
        c_next.c_prev = c_prev;

        if ( c_prev == c_next ) // faked-first node found
        {
            DefaultNode faked_first = c_next;
            
            if ( c_parent != null )
                c_parent.c_children = null;

            faked_first.c_next = faked_first.c_prev = faked_first.c_parent = null;
        }

        c_next = c_prev = c_parent = null;
    }
    
    /**
     *  Detach all children.
     */
    void detachChildren()
    {
        if ( c_children == null )
            return;

        DefaultNode finishNode   = c_children;
        DefaultNode iterator     = finishNode.c_next;
        
        while ( iterator != finishNode )
        {
            iterator = iterator.c_next;
            iterator.c_prev.detach();
        }
    }
    
    void calculateAccumulatedMatrix( Matrix4f m )
    {
    	m.reset();
    	
    	m.t( -m_px, -m_py, 0f );
    	m.s( m_sx, m_sy, 1f );
    	m.r( 0f, 0f, 1f, m_rotAngle );
    	m.t( m_tx, m_ty, 0f );
    }
    
    AccumulatedAttribute getAccumulatedAttrib()
    {
    	return m_accAttribute;
    }
    
    void markNeedUpdate( boolean needUpdate )
    {
    	m_needUpdate = needUpdate;
    }
    
    boolean needUpdate()
    {
    	return m_needUpdate;
    }
    
    DefaultNode c_prev;     //!< Previous node.
    DefaultNode c_next;     //!< Next node.
    DefaultNode c_children; //!< The faked first node of direct children.
    DefaultNode c_parent;   //!< The direct father of this node.
    
    private final String    m_name;
    private Rectangle       m_clipping;
    private float			m_tx, m_ty, m_rotAngle, m_sx, m_sy, m_px, m_py;
    private int             m_flags, m_planeColor, m_additionalColor;
    
    private DoublyLinkedList< Animator >    m_animators;
    private AccumulatedAttribute            m_accAttribute;
    private boolean					        m_needUpdate;
    //! Order for rendering, larger value means being rendered later.
    //! Top node always have rendering order = 0
    //! If rendering order is set to negative value, the actual rendering order
    //! number will be <direct parent's order> + 1.
    //! If two nodes have the same order number, the actual order will be
    //! decided by scene.
    private int						m_rOrder;
}